var crypto = require('crypto');
var through = require('through');
var duplexer = require('duplexer');
var concatStream = require('concat-stream');
var checkSyntax = require('syntax-error');

var mdeps = require('rebrowser-parse');
var browserPack = require('rebrowser-pack');
var browserResolve = require('browser-resolve');
var insertGlobals = require('insert-module-globals');
var umd = require('umd');

var path = require('path');
var inherits = require('inherits');
var EventEmitter = require('events').EventEmitter;

module.exports = function (files, excludeMap, isCompress) {
    return new Browserify(files, excludeMap, isCompress);
};

function hash(what) {
    return crypto.createHash('md5').update(what).digest('base64').slice(0, 6);
};

inherits(Browserify, EventEmitter);
function Browserify (files, excludeMap, isCompress) {
    this.files = [];
    this.excludeMap = excludeMap || {};
    this.isCompress = isCompress
    this.exports = {};
    this._pending = 0;
    this._entries = [];
    this._ignore = {};
    this._external = {};
    this._expose = {};
    this._mapped = {};
    this._transforms = [];
    
    [].concat(files).filter(Boolean).forEach(this.add.bind(this));
}

Browserify.prototype.add = function (file) {
    this.require(file, { entry: true });
    return this;
};

Browserify.prototype.require = function (id, opts) {
    var self = this;
    if (opts === undefined) opts = { expose: id };
    
    self._pending ++;
    
    var basedir = opts.basedir || process.cwd();
    var fromfile = basedir + '/_fake.js';
    
    var params = { filename: fromfile, packageFilter: packageFilter };
    browserResolve(id, params, function (err, file) {
        if (err) return self.emit('error', err);
        if (!file) return self.emit('error', new Error(
            'module ' + JSON.stringify(id) + ' not found in require()'
        ));
        
        if (opts.expose) {
            self.exports[file] = hash(file);
            
            if (typeof opts.expose === 'string') {
                self._expose[file] = opts.expose;
                self._mapped[opts.expose] = file;
            }
        }

        if (opts.external) {
            self._external[file] = true;
        }
        else {
            self.files.push(file);
        }
        
        if (opts.entry) self._entries.push(file);
        
        if (--self._pending === 0) self.emit('_ready');
    });

    return self;
};

// DEPRECATED
Browserify.prototype.expose = function (name, file) {
    this.exports[file] = name;
    this.files.push(file);
};

Browserify.prototype.external = function (id, opts) {
    opts = opts || {};
    opts.external = true;
    return this.require(id, opts);
};

Browserify.prototype.ignore = function (file) {
    this._ignore[file] = true;
    return this;
};

Browserify.prototype.bundle = function (opts, cb) {
    var self = this;
    if (typeof opts === 'function') {
        cb = opts;
        opts = {};
    }
    if (!opts) opts = {};
    if (opts.insertGlobals === undefined) opts.insertGlobals = false;
    if (opts.detectGlobals === undefined) opts.detectGlobals = true;
    if (opts.ignoreMissing === undefined) opts.ignoreMissing = false;
    if (opts.standalone === undefined) opts.standalone = false;
    
    opts.resolve = self._resolve.bind(self);
    opts.transform = self._transforms;
    opts.transformKey = [ 'browserify', 'transform' ];

    /**
     * 勿读集合传递到 module-deps 组件
     * */
    opts.excludeMap = self.excludeMap;
    var fileIDMap = JSON.parse(JSON.stringify((self.excludeMap)));

    var extcb = function(err, content){
        if(cb){
            cb(err, content, fileIDMap);
        }
    };

    var parentFilter = opts.packageFilter;
    opts.packageFilter = function (pkg) {
        if (parentFilter) pkg = parentFilter(pkg);
        return packageFilter(pkg);
    };
    
    if (self._pending) {
        var tr = through();
        self.on('_ready', function () {
            /**
             * 此处不能将cb改为extcb的
             * */
            var b = self.bundle(opts, cb);
            if (!extcb) b.on('error', tr.emit.bind(tr, 'error'));
            b.pipe(tr);
        });
        return tr;
    }
    
    var d = self.deps(opts);
    var g = opts.detectGlobals || opts.insertGlobals
        ? insertGlobals(self.files, {
            resolve: self._resolve.bind(self),
            always: opts.insertGlobals
        })
        : through()
    ;
    var p = self.pack(opts.debug, opts.standalone, fileIDMap);

    p.on('error', extcb);
    p.pipe(concatStream(extcb));
    
    d.on('error', p.emit.bind(p, 'error'));
    g.on('error', p.emit.bind(p, 'error'));
    d.pipe(g).pipe(p);
    
    return p;
};

Browserify.prototype.transform = function (t) {
    if (typeof t === 'string' && /^\./.test(t)) {
        t = path.resolve(t);
    }
    this._transforms.push(t);
    return this;
};

Browserify.prototype.deps = function (opts) {
    var self = this;
    var d = mdeps(self.files, opts);
    var tr = d.pipe(through(write));
    d.on('error', tr.emit.bind(tr, 'error'));
    return tr;
    
    function write (row) {
        if (row.id === emptyModulePath) {
            row.source = '';
        }
        
        if (self._expose[row.id]) {
            this.queue({
                exposed: self._expose[row.id],
                deps: {},
                source: 'module.exports=require(\'' + hash(row.id) + '\');'
            });
        }
        
        if (self.exports[row.id]) row.exposed = self.exports[row.id];

        // skip adding this file if it is external
        if (self._external[row.id]) {
            return;
        }
       
        if (/\.json$/.test(row.id)) {
            row.source = 'module.exports=' + row.source;
        }
        
        var ix = self._entries.indexOf(row.id);
        row.entry = ix >= 0;
        if (ix >= 0) row.order = ix;
        this.queue(row);
    }
};

/**
 * 文件名称指纹(编码)的生成，成功后放入 已用编码池
 * */
Browserify.prototype.generFileCode = function(fileName, fileCodePool){
    var resCode = 1;
    var dirLevel = 1;
    for(var i = 0;i < fileName.length;i++){
        var currNum = fileName.charCodeAt(i);
        //console.log(fileName[i] + " " + currNum);
        /**
         * 遇分隔符'.'46 或 '\'92
         * */
        if(currNum == 46){
            resCode ^= (8 * dirLevel);
        }else if(currNum == 92){
            resCode += resCode/6;
            dirLevel += 1;
        }else{
            /**
             * 偶数相加 ， 奇数相减
             * */
            if(currNum % 2){
                resCode -= (currNum+ 31) * dirLevel ;
            }else{
                resCode += (currNum + 76) * dirLevel;
            }
        }
        resCode += (i % 60)*5;
    }

    if(resCode < 0){
        resCode = 0 - resCode;
    }

    /**
     * 冲突则抛出异常，提示文件重命名
     * */
    if(fileCodePool.indexOf(resCode) >=0 ){
        console.error("Generate FileCode(" + fileName + ") " + resCode + " Conflict In Pool:" + fileCodePool.join(",") +
            "\nPlease Rename File " + fileName);
        throw new Error("Generate FileCode Conflict " + fileName);
    }

    fileCodePool.push(resCode);
    return resCode;
};

Browserify.prototype.fileCodeInit = function(){
    var self = this;
    //console.log("file code init:" + JSON.stringify(self.excludeMap));

    var resAry = [];
    for(var key in self.excludeMap){
        resAry[resAry.length] = self.excludeMap[key];
    }
    return resAry;
};

Browserify.prototype.pack = function (debug, standalone, fileIDMap) {
    var self = this;

    var fileCodePool = self.fileCodeInit();
    //console.log("+++ " + fileCodePool.length);
    var packer = browserPack({ raw: true , isMaster: fileCodePool.length == 0, isCompress: self.isCompress});

    //var fileIDMap = self.excludeMap;
    var mainModule;
    /**
     * row.id的转换  将文件路径 转换为对应的编号ID
     * row.deps的转换 将依赖数组 转换为 依赖对象ID查找的MAP
     * */
    var input = through(function (row) {
        var ix;

        if (debug) { 
            row.sourceRoot = 'file://localhost'; 
            row.sourceFile = row.id;
        }

        if (row.exposed) {
            ix = row.exposed;
        }
        else {
            /**
             * 文件是否已加入输出集合
             * */
            ix = fileIDMap[row.id] !== undefined ? fileIDMap[row.id] : self.generFileCode(row.id, fileCodePool);
        }
        if (fileIDMap[row.id] === undefined) fileIDMap[row.id] = ix;
        
        if (/^#!/.test(row.source)) row.source = '//' + row.source;
        var err = checkSyntax(row.source, row.id);
        if (err) return self.emit('error', err);
        
        row.id = ix;
        if (row.entry) mainModule = mainModule || ix;
        row.deps = Object.keys(row.deps).reduce(function (acc, key) {
            var file = row.deps[key];

            // reference external and exposed files directly by hash
            if (self._external[file] || self.exports[file]) {
                acc[key] = hash(file);
                return acc;
            }
            /**
             * 文件未加入输出集合
             * */
            if (fileIDMap[file] === undefined) fileIDMap[file] = self.generFileCode(file, fileCodePool);
            acc[key] = fileIDMap[file];
            return acc;
        }, {});
        
        this.queue(row);
    });
    
    var first = true;
    var hasExports = Object.keys(self.exports).length;
    var output = through(write, end);
    
    function writePrelude () {
        if (!first) return;
        if (standalone) {
            return output.queue(umd.prelude(standalone) + 'return ');
        }
        if (!hasExports) return output.queue('');
        output.queue('require=');
    }
    
    input.pipe(packer);
    packer.pipe(output);
    return duplexer(input, output);
    
    function write (buf) {
        if (first) writePrelude();
        first = false;
        /**
         * 将编译后的各个模块代码 加入 预输出队列
         * */
        this.queue(buf);
    }
    
    function end () {
        if (first) writePrelude();
        if (standalone) {
            output.queue('(' + mainModule + ')' + umd.postlude(standalone));
        }
        //this.queue('\n;');
        this.queue(null);
    }
};

var packageFilter = function (info) {
    if (typeof info.browserify === 'string' && !info.browser) {
        info.browser = info.browserify;
        delete info.browserify;
    }
    return info;
};

var emptyModulePath = require.resolve('./_empty');
Browserify.prototype._resolve = function (id, parent, cb) {
    var self = this;
    var result = function (file, x) {
        self.emit('file', file, id, parent);
        cb(null, file, x);

    };
    if (self._mapped[id]) return result(self._mapped[id]);
    
    return browserResolve(id, parent, function(err, file) {
        if (err) return cb(err);
        /**
         * var fileIDMap = JSON.parse(JSON.stringify((self.excludeMap)));
         * 即fileIDMap带有初始值 会进入正面判断，实时开发会抛出异常， unknown reason until now , 但不影响最终合包文件逻辑
         * */
        /**
        if (!file) return cb(new Error('module '
            + JSON.stringify(id) + ' not found from '
            + JSON.stringify(parent.filename)
        ));
        */
        if (!file) return cb(null, emptyModulePath);
        if (self._ignore[file]) return cb(null, emptyModulePath);
        if (self._external[file]) return result(file, true);
        
        result(file);
    });
};
